/*
 *	http://code.google.com/p/velleman-usb-experiment-board-tool/
 *
 *	Library for talking to Velleman(c) 8055 USB Experiment Interface Board
 *	Also known as: VM110, P8055, K8055
 *
 *	Ref: http://www.velleman.eu/products/view/?id=351346
 *
 *	$Id: libv8055.c 5 2012-04-08 16:02:33Z cregganna@gmail.com $
 *
 *	Author: Tony.Jewell@Cregganna.Com
 *
 *	License: New BSD:
 *	Copyright (c) 2012, Tony.Jewell@Cregganna.Com
 *	All rights reserved.
 *
 *	Redistribution and use in source and binary forms, with or without
 *	modification, are permitted provided that the following conditions are
 *	met:
 *
 *	Redistributions of source code must retain the above copyright notice,
 *	this list of conditions and the following disclaimer.
 *	Redistributions in binary form must reproduce the above copyright
 *	notice, this list of conditions and the following disclaimer in the
 *	documentation and/or other materials provided with the distribution.
 *
 *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 *	IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 *	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *	HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *	LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include "libv8055.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hiddev.h>
#include <errno.h>

#define	FALSE 	(0)
#define	TRUE	(!FALSE)

#define V8055_MAGIC	((unsigned)0x8055544A)

/*
 * These are the bits for the digital inputs
 */
#define V8055_DIGITAL_INPUT_1	((unsigned)0x10)
#define V8055_DIGITAL_INPUT_2	((unsigned)0x20)
#define V8055_DIGITAL_INPUT_3	((unsigned)0x01)	/* Why is this not in sequence? */
#define V8055_DIGITAL_INPUT_4	((unsigned)0x40)
#define V8055_DIGITAL_INPUT_5	((unsigned)0x80)

#define FIX_DI(value) \
{ \
	unsigned new_value = (value & V8055_DIGITAL_INPUT_3) << 2;			\
	new_value |= (value & (V8055_DIGITAL_INPUT_1 | V8055_DIGITAL_INPUT_2) ) >> 4;	\
	new_value |= (value & (V8055_DIGITAL_INPUT_4 | V8055_DIGITAL_INPUT_5) ) >> 3;	\
	value = new_value;								\
}

/*
 * These could, in future, be sourced from hiddev_field_info.application
 */
#define	INPUT_USAGE_BASE			((unsigned)0xff000000)
#define	OUTPUT_USAGE_BASE			((unsigned)0xff000000)
#define MK_INPUT_USAGE(code)			(INPUT_USAGE_BASE | (code))
#define MK_OUTPUT_USAGE(code)			(OUTPUT_USAGE_BASE | (code))

#define INPUT_USAGE_DIGITAL_INPUTS		MK_INPUT_USAGE(1)
#define INPUT_USAGE_BOARD_ADDR			MK_INPUT_USAGE(2)
#define	INPUT_USAGE_ANALOG_INPUT1		MK_INPUT_USAGE(3)
#define	INPUT_USAGE_ANALOG_INPUT2		MK_INPUT_USAGE(4)
#define	INPUT_USAGE_DIGITAL_INPUT1_COUNT_LSB	MK_INPUT_USAGE(5)
#define	INPUT_USAGE_DIGITAL_INPUT1_COUNT_MSB	MK_INPUT_USAGE(6)
#define	INPUT_USAGE_DIGITAL_INPUT2_COUNT_LSB	MK_INPUT_USAGE(7)
#define	INPUT_USAGE_DIGITAL_INPUT2_COUNT_MSB	MK_INPUT_USAGE(8)

#define OUTPUT_USAGE_CMD			MK_OUTPUT_USAGE(1)
#define OUTPUT_USAGE_DIGITAL_OUTPUTS		MK_OUTPUT_USAGE(2)
#define OUTPUT_USAGE_ANALOG_OUTPUT1		MK_OUTPUT_USAGE(3)
#define OUTPUT_USAGE_ANALOG_OUTPUT2		MK_OUTPUT_USAGE(4)

enum v8055_cmd {
	CMD_SET_DIGITAL_ANALOG	= 5
};

struct v8055_dp {
	unsigned long		magic;
	char			*device;
	int			fd;
	struct v8055_board_info	info;
};

extern v8055 v8055_open(char *hid_device, int for_writing)
{
	int open_flags = (for_writing? O_RDWR: O_RDONLY);
	int fd = open(hid_device, open_flags);
	if (fd < 0) {
		return NULL;
	}

	struct v8055_dp *v8055 = calloc(sizeof(struct v8055_dp), 1);
	if (v8055 == NULL) {
		return NULL;
	}
	v8055->magic = V8055_MAGIC;

	struct hiddev_devinfo devinfo;
	if (ioctl(fd, HIDIOCGDEVINFO, &devinfo) < 0
		|| ioctl(fd, HIDIOCGNAME(sizeof(v8055->info.name)), v8055->info.name) < 0) {
		return NULL;
	}

	v8055->device = strdup(hid_device);
	v8055->fd = fd;
	v8055->info.vendor_id = devinfo.vendor;
	v8055->info.product_id = devinfo.product;
	v8055->info.version = devinfo.version;

	return v8055;
}

extern void v8055_close(v8055 board)
{
	struct v8055_dp *v8055 = board;
	if (v8055->magic != V8055_MAGIC) {
		errno = EBADFD;
		return;
	}

	close(v8055->fd);

	v8055->magic = 0;
	(void)free(v8055);
}


extern int v8055_validate(v8055 board)
{
	struct v8055_dp *v8055 = board;
	if (v8055->magic != V8055_MAGIC) {
		errno = EBADFD;
		return FALSE;
	}

	if (v8055->info.vendor_id != V8055_VENDOR_ID
		|| v8055->info.product_id != V8055_PRODUCT_ID) {
		return FALSE;
	}

	return TRUE;
}

extern int v8055_get_board_info(v8055 board, struct v8055_board_info *info)
{
	struct v8055_dp *v8055 = board;
	if (v8055->magic != V8055_MAGIC) {
		errno = EBADFD;
		return -1;
	}

	memcpy(info, &v8055->info, sizeof(*info));
	return 0;
}

extern int v8055_get_value(v8055 board, enum v8055_property property)
{
	struct v8055_dp *v8055 = board;
	if (v8055->magic != V8055_MAGIC) {
		errno = EBADFD;
		return -1;
	}

	// Look up the value for the usage code directly
	struct hiddev_usage_ref uref[2];
	int num;
	for (num = 0; num < (sizeof(uref) / sizeof(*uref)); num++) {
		memset(&uref[num], 0, sizeof(uref[0]));
		uref[num].report_id = HID_REPORT_ID_UNKNOWN;
	}

	int num_urefs;
	switch (property) {
	case V8055_DIGITAL_INPUTS:
		num_urefs = 1;
		uref[0].report_type = HID_REPORT_TYPE_INPUT;
		uref[0].usage_code = INPUT_USAGE_DIGITAL_INPUTS;
		break;
	case V8055_BOARD_ADDR:
		num_urefs = 1;
		uref[0].report_type = HID_REPORT_TYPE_INPUT;
		uref[0].usage_code = INPUT_USAGE_BOARD_ADDR;
		break;
	case V8055_ANALOG_INPUT1:
		num_urefs = 1;
		uref[0].report_type = HID_REPORT_TYPE_INPUT;
		uref[0].usage_code = INPUT_USAGE_ANALOG_INPUT1;
		break;
	case V8055_ANALOG_INPUT2:
		num_urefs = 1;
		uref[0].report_type = HID_REPORT_TYPE_INPUT;
		uref[0].usage_code = INPUT_USAGE_ANALOG_INPUT2;
		break;
	case V8055_DIGITAL_INPUT1_COUNT:
		num_urefs = 2;
		// Get MSB then LSB
		uref[0].report_type = HID_REPORT_TYPE_INPUT;
		uref[0].usage_code = INPUT_USAGE_DIGITAL_INPUT1_COUNT_MSB;
		uref[1].report_type = HID_REPORT_TYPE_INPUT;
		uref[1].usage_code = INPUT_USAGE_DIGITAL_INPUT1_COUNT_LSB;
		break;
	case V8055_DIGITAL_INPUT2_COUNT:
		num_urefs = 2;
		// Get MSB then LSB
		uref[0].report_type = HID_REPORT_TYPE_INPUT;
		uref[0].usage_code = INPUT_USAGE_DIGITAL_INPUT2_COUNT_MSB;
		uref[1].report_type = HID_REPORT_TYPE_INPUT;
		uref[1].usage_code = INPUT_USAGE_DIGITAL_INPUT2_COUNT_LSB;
		break;
	case V8055_DIGITAL_OUTPUTS:
		num_urefs = 1;
		uref[0].report_type = HID_REPORT_TYPE_OUTPUT;
		uref[0].usage_code = OUTPUT_USAGE_DIGITAL_OUTPUTS;
		break;
	case V8055_ANALOG_OUTPUT1:
		num_urefs = 1;
		uref[0].report_type = HID_REPORT_TYPE_OUTPUT;
		uref[0].usage_code = OUTPUT_USAGE_ANALOG_OUTPUT1;
		break;
	case V8055_ANALOG_OUTPUT2:
		num_urefs = 1;
		uref[0].report_type = HID_REPORT_TYPE_OUTPUT;
		uref[0].usage_code = OUTPUT_USAGE_ANALOG_OUTPUT2;
		break;
	}
	unsigned result = 0;
	for (num = 0; num < num_urefs; num++) {
		if (ioctl(v8055->fd, HIDIOCGUSAGE, &uref[num]) < 0) {
			return -1;
		}
		unsigned value = uref[num].value;
		if (uref[num].usage_code == INPUT_USAGE_DIGITAL_INPUTS) {
			FIX_DI(value);
		}
		result = (result << 8) | value;
	}

	return result;
}


extern int v8055_put_value(v8055 board, enum v8055_property property, u_int value)
{
	struct v8055_dp *v8055 = board;
	if (v8055->magic != V8055_MAGIC) {
		errno = EBADFD;
		return -1;
	}

	struct hiddev_usage_ref cmd_uref;
	struct hiddev_usage_ref uref;
	struct hiddev_report_info rinfo;
	struct hiddev_field_info finfo;

	memset(&cmd_uref, 0, sizeof(cmd_uref));
	cmd_uref.report_type = HID_REPORT_TYPE_OUTPUT;
	cmd_uref.report_id = HID_REPORT_ID_UNKNOWN;
	cmd_uref.usage_code = OUTPUT_USAGE_CMD;

	memset(&uref, 0, sizeof(uref));
	uref.report_type = HID_REPORT_TYPE_OUTPUT;
	uref.report_id = HID_REPORT_ID_UNKNOWN;
	switch (property) {
	case V8055_DIGITAL_OUTPUTS:
		uref.usage_code = OUTPUT_USAGE_DIGITAL_OUTPUTS;
		break;
	case V8055_ANALOG_OUTPUT1:
		uref.usage_code = OUTPUT_USAGE_ANALOG_OUTPUT1;
		break;
	case V8055_ANALOG_OUTPUT2:
		uref.usage_code = OUTPUT_USAGE_ANALOG_OUTPUT2;
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	// Get the Field indexes.
	if (ioctl(v8055->fd, HIDIOCINITREPORT) < 0) {
		return -1;
	}
	if (ioctl(v8055->fd, HIDIOCGUSAGE, &cmd_uref) < 0) {
		return -1;
	}
	if (ioctl(v8055->fd, HIDIOCGUSAGE, &uref) < 0) {
		return -1;
	}

	// Get the field info
	finfo.report_type = uref.report_type;
	finfo.report_id = uref.report_id;
	finfo.field_index = uref.field_index;
	if (ioctl(v8055->fd, HIDIOCGFIELDINFO, &finfo) < 0) {
		return -1;
	}

	if (value < finfo.logical_minimum
		|| value > finfo.logical_maximum) {
		errno = EINVAL;
		return -1;
	}

	// Set the cmd:
	cmd_uref.value = CMD_SET_DIGITAL_ANALOG;
	if (ioctl(v8055->fd, HIDIOCSUSAGE, &cmd_uref) < 0) {
		return -1;
	}
	// Set the value:
	uref.value = value;
	if (ioctl(v8055->fd, HIDIOCSUSAGE, &uref) < 0) {
		return -1;
	}

	// Post the command to the device.
	rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
	rinfo.report_id = uref.report_id;
	rinfo.num_fields = 2;
	if (ioctl(v8055->fd, HIDIOCSREPORT, &rinfo) < 0) {
		return -1;
	}
	return uref.value;
}
